#include "Server.hpp"
#include "UserManager.hpp"
#include <memory>
#include <fstream>
#include <unordered_map>
#include <signal.h>

using namespace Server;

const std::string dictTxt = "dict.txt";

static unordered_map<string, string> dict;

static const string cutoff = "#";


void Usage(string proc)
{
	std::cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}

// 以#为界限进行插入
// static const string cutoff = "#";

bool cutstring(const string &str)
{
	auto pos = str.find(cutoff);
	if (pos == string ::npos)
		return false;
	else
	{
		string key, val;
		// substr是一个左闭右开区间
		key = str.substr(0, pos);
		// 这样子写可以自由且灵活的更改分割符
		val = str.substr(pos + cutoff.size());
		dict.insert(make_pair(key, val));
	}
}

void Init_Diction()
{
	ifstream in(dictTxt);

	if (!in.is_open())
	{
		cerr << "open file " << dictTxt << " error" << endl;
		exit(OPEN_ERR);
	}

	string line;
	// cout<<"测试获取文件内容"<<endl;
	while (getline(in, line))
	{
		// cout<<line<<endl;
		// 切割字符串放入map中
		cutstring(line);
	}

	in.close();
	cout << "已成功装载字典" << endl;
}

// 任务1.字典
void Dictionary(int socket_fd, string client_IP, uint16_t client_port, string message)
{
	// 此时已初始化完毕字典，在业务处理时比对对应的字符串

	// 但是业务的处理并不是简单的打印，此时已经转换为双向交流的逻辑，所以我们还要把获取的信息发回给客户端。

	string responce;

	auto pos = dict.find(message);
	if (pos == dict.end())
		responce = "unkown";
	else
		responce = pos->second; // 把获取到的对应翻译的结果准备发送回去

	// 准备发送流程，此时的服务器就充当一次客户端，不需要再显示的绑定，直接把信息送回客户端
	struct sockaddr_in client;
	bzero(&client, sizeof(client));
	// 填写SockerAddr字段

	client.sin_family = AF_INET;
	// 地址需要转换
	client.sin_port = htons(client_port);
	client.sin_addr.s_addr = inet_addr(client_IP.c_str());

	//  调用发送函数
	sendto(socket_fd, responce.c_str(), responce.size(), 0, (sockaddr *)&client, sizeof(client));
}

// void handler(int signum)
// {
// 	Init_Diction();
// }

//

// 任务2.模拟远程Shell
void NetShell(int socket_fd, string client_IP, uint16_t client_port, string message)
{
	// 我们之前实现的模拟Shell，是借助fork和程序替换函数实现的，其中的逻辑比较麻烦，但是其实有一步到位的函数。
	//  原步骤: 1. 解析Cmd 2.执行fork,让子进程替换程序为目标程序,主进程继续解析cmd

	// popen函数可以一次性帮助我们直接执行完以上所有的步骤
	// FILE *popen(const char *command, const char *type);
	// 同字典,我们拿了信息也是要说话的

	if (message.find("rm") != string::npos || message.find("mv") != string::npos || message.find("rmdir") != string::npos)
	{
		cerr << client_IP << ":" << client_port << " 正在做一个非法的操作: " << message << endl;
		return;
	}

	string responce;
	FILE *fp = popen(message.c_str(), "r");
	if (fp == nullptr)
		responce = message + " exec failed";

	// 那么,拿取到了对应的结果,现在服务器执行了对应的Shell命令,那么该如何返回给我们的服务端呢?
	// Linux下一切皆文件,直接从对应的文件指针内部读取数据即可
	char line[1024];
	while (fgets(line, sizeof(line), fp))
	{
		responce += line;
	}
	pclose(fp);

	// 返回得到的消息
	struct sockaddr_in client;
	bzero(&client, sizeof(client));

	client.sin_family = AF_INET;
	client.sin_port = htons(client_port);
	client.sin_addr.s_addr = inet_addr(client_IP.c_str());

	sendto(socket_fd, responce.c_str(), responce.size(), 0, (struct sockaddr *)&client, sizeof(client));
}

// 任务3.网络聊天室
// 网络聊天室为所有的用户进行广播

// 1.一旦用户链接上了服务器，就提示目标用户输入online进入聊天室
// 2.如果当前用户处于在线状态，就对所有当前处于在线用户状态的用户发送消息。

OnlineUser userlist;

void ChatRoom(int socket_fd, string client_IP, uint16_t client_port, string message)
{
	// add_user(const uint16_t& port, const string& IP )
	if(message == "online")  userlist.add_user(client_port,client_IP);

	if(message == "offline") userlist.del_user(client_port,client_IP);
	
	
	if(userlist.isOnline(client_port,client_IP))
	{
		// void boardcast(int socket_fd, string &message, const uint16_t &port, const string &IP)

		userlist.boardcast(socket_fd,message,client_port,client_IP);
	}
	else
	{

		struct sockaddr_in client;
		bzero(&client, sizeof(client));

		client.sin_family = AF_INET;
		client.sin_port = htons(client_port);
		client.sin_addr.s_addr = inet_addr(client_IP.c_str());

		string response = "你还没有上线，请输入online进行上线";

		sendto(socket_fd, response.c_str(), response.size(), 0, (struct sockaddr *)&client, sizeof(client));

	}


}




int main(int argc, char *argv[])
{
	// 输入错误的时候打印手册

	// 编写一个热更新函数，也就是不重新编译，借助信号来实现更新字库
	// signal(2,handler);

	if (argc != 2)
	{
		Usage(argv[0]);
		exit(-1);
	}

	// 获取命令行参数
	uint16_t server_port = atoi(argv[1]);

	// 任务1.字典服务
	// 1.要实现一个字典，先将对应的文本获取上来，通过读取对应文件的方式来获取。

	// Init_Diction();



	unique_ptr<UDPServer> pServer(new UDPServer(ChatRoom, server_port));

	pServer->init();
	pServer->start();

	return 0;
}